from langchain_core.output_parsers import JsonOutputParser

from interview_voice_project.__001__langgraph_more_node import AgentState
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field
from typing import List
import re

from interview_voice_project.__003__fastapi import update_mysql
from interview_voice_project.common import my_llm


# 定义 Pydantic 模型，用于标准化 JSON 输出
class InterviewItem(BaseModel):
    question: str = Field(description="面试官提出的问题")
    user_answer: str = Field(description="面试者的回答")


class InterviewTopics(BaseModel):
    interview_topic: List[InterviewItem]


# 使用 JsonOutputParser 确保输出为 JSON
parser = JsonOutputParser(pydantic_object=InterviewTopics)

# 定义提示词
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是面试分析助手，请提取面试官的问题和面试者的回答，并对内容进行适度润色。"),
    ("user", """
以下是一段【面试语音转写文本】，其中包含大量口头语、语气词（如“嗯”、“啊”、“就是”）和不完整句子。
请你完成以下任务：

1. 识别面试官的问题，去除冗余口头语，使问题表达清晰、完整。  
2. 提取面试者的回答，去除语气词，对内容进行适度润色，保持原意但更流畅、正式。  
3. 保持输出为标准 JSON，结构如下：
{format_instructions}

面试文本：
{voice_text}
""")
]).partial(format_instructions=parser.get_format_instructions())


def split_text_with_overlap(text: str, chunk_size: int = 2000, overlap_size: int = 200) -> List[str]:
    """
    将文本切分成重叠的块
    
    Args:
        text: 输入文本
        chunk_size: 每块的大小
        overlap_size: 重叠部分的大小
        
    Returns:
        切分后的文本块列表
    """
    if len(text) <= chunk_size:
        return [text]
    
    chunks = []
    start = 0
    
    while start < len(text):
        end = start + chunk_size
        
        # 如果不是最后一块，尝试在句号、问号、感叹号处切分
        if end < len(text):
            # 寻找最近的句号、问号、感叹号
            for i in range(end, max(start + chunk_size - 100, start), -1):
                if text[i] in '。？！':
                    end = i + 1
                    break
        
        chunk = text[start:end]
        chunks.append(chunk)
        
        # 下一块的开始位置，考虑重叠
        start = end - overlap_size
        if start >= len(text):
            break
    
    return chunks


def merge_interview_results(results: List[dict]) -> List[dict]:
    """
    融合多个抽取结果，去重并合并
    
    Args:
        results: 多个抽取结果列表
        
    Returns:
        融合后的结果列表
    """
    merged_items = []
    seen_questions = set()
    
    for result in results:
        if 'interview_topic' in result:
            for item in result['interview_topic']:
                question = item['question'].strip()
                # 简单的去重逻辑：如果问题相似度很高，跳过
                if not any(_is_similar_question(question, seen_q) for seen_q in seen_questions):
                    merged_items.append(item)
                    seen_questions.add(question)
    
    return merged_items


def _is_similar_question(q1: str, q2: str, threshold: float = 0.8) -> bool:
    """
    判断两个问题是否相似
    
    Args:
        q1: 问题1
        q2: 问题2
        threshold: 相似度阈值
        
    Returns:
        是否相似
    """
    # 简单的相似度计算：基于字符重叠
    if not q1 or not q2:
        return False
    
    # 去除标点符号和空格
    q1_clean = re.sub(r'[^\w]', '', q1)
    q2_clean = re.sub(r'[^\w]', '', q2)
    
    if len(q1_clean) == 0 or len(q2_clean) == 0:
        return False
    
    # 计算字符重叠度
    common_chars = set(q1_clean) & set(q2_clean)
    similarity = len(common_chars) / max(len(q1_clean), len(q2_clean))
    
    return similarity >= threshold


async def extract_interview_topic_node(state: AgentState):
    """
    使用大模型提取面试题目和回答，支持文本切块处理和融合
    """
    await update_mysql("开始抽取面试题", record_id=state["record_id"])
    voice_text = state["voice_arrange_text"]

    # 创建 chain
    chain = prompt | my_llm

    # 1. 文本切块处理
    await update_mysql("开始文本切块处理", record_id=state["record_id"])
    text_chunks = split_text_with_overlap(voice_text, chunk_size=2000, overlap_size=200)
    await update_mysql(f"文本已切分为 {len(text_chunks)} 个块", record_id=state["record_id"])

    # 2. 对每个块进行抽取
    all_results = []
    for i, chunk in enumerate(text_chunks):
        print(f"正在处理第 {i+1}/{len(text_chunks)} 个文本块")
        await update_mysql(f"正在处理第 {i + 1}/{len(text_chunks)} 个文本块", record_id=state["record_id"])
        
        # 流式输出当前块的处理结果
        collected_text = ""
        for chunk_result in chain.stream({"voice_text": chunk}):
            token = chunk_result.content
            print(token, end="", flush=True)  # 实时打印
            collected_text += token
        
        print(f"\n--- 第 {i+1} 块流式结束 ---")
        # await put_think_msg_and_update_mysql(f"\n--- 第 {i + 1} 块流式结束 ---", record_id=state["record_id"])
        
        # 解析当前块的结果
        try:
            result = parser.parse(collected_text)
            all_results.append(result)
        except Exception as e:
            await update_mysql(f"第 {i + 1} 块解析失败: {e}", record_id=state["record_id"])
            continue

    # 3. 融合所有结果
    await update_mysql("开始融合抽取结果", record_id=state["record_id"])
    merged_items = merge_interview_results(all_results)
    
    state["interview_topic_list"] = merged_items
    await update_mysql(f"完成抽取面试题，共提取 {len(merged_items)} 个问答对", record_id=state["record_id"])
    return state


if __name__ == '__main__':
    import asyncio
    asyncio.run(extract_interview_topic_node({
        "voice_arrange_text": "哎那个罗培京你好嗯，你好，孙先生啊，我能听到。那我们现在开始吧。好的好的，您先那个自我介绍一下，然后能不能打开摄像头看一看。好的。嗯，面试官您好，我叫小明呃，毕业于广东外贸大学南国商学院物联网工程专业。在校期间通过自学一些AR知识，包括NRP领域的呃深度学习机器学习，以及呃一些大模型的前沿的技术方向。比如呃RG等等。然后呃在毕业后从事了一个呃基础的一个数据的处理工作。然后后面近期的一个项目是一个电商RG智能客服项目。嗯，还有一个知识图谱，然后运动医学领域的一个呃知识图谱的一个构建工作。呃，你好，这是我的基本介绍。呃，面试官。就是是从毕业之后做了做了一年的这个agent开发，对吧？对的对的，就但是负责的工作比较基础，主要是数据的一些清洗和处理。然后包括模型框架的一些基本的构建。然后后面的话包括一些部署啊，或者上线，就不是我的工作范畴。嗯，一年里面主要搞了三个项目，呃后面后面那个第三个项目，他那个文本分类，它是我实习期间的一个项目。哦，那主要做的是第一个是吧？对，这个是最新的一个，就是最近的比较就是掌握的比较好一个。这个项目里面有多少人啊？嗯，这个项目有6个人嗯。你你是主要负责哪一块？呃，这边的话就是先包括一个数据的一个清洗。然后后面的话就是呃把circle的一个模块以及那个RG的一个集成。就是我主要是负责这个，然后在中间有一个意图识别的那个我就协助一下。对的。这个里面主要做的什么工作呀？电商re智能客服啊，主要有哪些这个这个呃。是什么样的客服啊？呃，他这个系统呢？就是呃针对传统这个客服的就电商平台客服，他这个价格或者商品的一些促销活动，以及他的一些售后政策的一些时效性。呃，针对传统客服这样的一个可以说是一个缺口吧。但是我我们就针对这一个缺口进行一个开发。就是呃将一将近期的就比如说两个月之内的一些。新的呃电商电商的一些价格上上新的一些产品，以及它更新的一些售后政策，就是呃就是处理成一个文本，然后我们把它。做成一个外挂的一个知识库。然后这样的话我们就在当用户输入一个问题的时候，比如说他要查询呃，这这款手机它现在的一个实这个价格是多少。然后它的历史价格是多少，以及它这个历史的价格曲线呃，甚至是他这个商家，它对于这款手机它的一个售后的政策优惠的政策，它是怎样的？我们就是呃将这个这些知识输入。给你这个系统，然后系统就会进行一个文本的上下文的一个识别，然后让让这个上下文拼接到这个问题上面，然后输入给大模型。这样大模型它生成一个答案的话，它是较为精准的，而且它的时效性相比传统的一个智能客服的话，他是更强了，而且它的准确性会会更高一些。呃，他的召回率也会更强一些，嗯，主要是这样子。to C端的用户运用的是吧？呃，对是吧？对，这个主要是一个to C的项目。但是他其实他其实面对的两个一个用户群体啊，就是他既 to C又to B吧，就只可以这么说。因为他呃电商平台他也需要一些呃查询，就是双方双方的一些信息，他可能就是说这个用户这个商品，他近期为多少的用户访问了这个商品的这个这个销量如何。然后这个商品的售后，他的反馈怎么样，他的评论会怎么样。呃，双方也进行一个收集。呃，所以的话他是面向又面向这个客服团队，又面向这个消费者。那这个客服团队和消费者之间有什么本质的区别啊？嗯，就因为他们的这个定位不一样。就比如说我们呃呃这个boss平台，boss平台它既有一个呃呃就是招聘，就是贵司像降您这样的这样的招聘者需要就是呃进行一个招聘。然后我们作为求职者的话，也需要进行一个应聘嘛。就是这样那个他的用双方的这个功能，就是开发的一个功能，它不一样，就是。嗯，那怎么是做到这个就比如说查询之间的一些安全隔离的。就比如说用户要查这个设备的销量，商品的商品的这个差评。然后这些都都能查到吗？对的，可以的。因为我我们就是呃基本基本上它的一个周期，就是这个知识入库的更新的周期，我们是两个月1次，这样的话我们定期的就是在电商平台上，比如这个这个爬取他们的一些这个评论，然后收集起来，然后进行一个就是评论的回收。这样的话我们是可以的。至于这个安全隔离的话，呃我们。呃，有环境隔离，就比如说一个杀伤机制嘛，这这个它就是将呃。客户客户这边客户端这边的，我们进行一个一个存储。然后在这个团队这边，客服团队这边也进行一个就是他们的一些评论或者一些信息消息的存储，这样的话比较安全一些。使用的容器的话基基本上。就是有有一个容器化技术嘛，就是说比如docker，就是这样的话，它它毕竟比较轻量级，然后它的资源开销比较小，这样的话也益于我们使用和这个药配。有没有那种不太适合让用户to C端用户查的，但是呢B端用户有强蓄的这种嗯检做呀。呃，您的意思是说呃，这B端用户他需求比较高，但是他C端用户不能查询是吗？对，有没有那种就是现在优惠额看你全网的所有商品每一个商品的销量，这不是覆盖面太广了嘛？这种能场吧？嗯，这边的话主要是针对于大这个用户销量比较这个怎么说，用户量比较大的平台，因为那些小平台的话基本上没有。因为在小平台上。这个第一个他们这个用户基数毕竟没有这个大平台那么多。第二个就是呃在用户群体比较小的平台上面的话，我们做这个系统，他能收集到数据也不多。你这个电商有多少的量啊？现在嗯基本您您是说那个每日的访问量吗？就是你这个电商re哥智能客服，用户是要查商品销量的，商品销量销售额是什么样的，你这个电商是卖啥的啊，销销量销售额。嗯，比如说一些消费电子类的，然后还有一些数码产品，嗯，这样子呃，还有一些日常的日用品，这些都都有的。你是去京东上爬数据吗？京东淘宝爬出据吗？对的。呃，就比如说呃也也例如呃那些二手平台，就比如说闲鱼也有，或者说转转这种大的一些二手的闲置转卖平台，我们也有去进行一些合法的part取。你这个抓取能合法吗？就你只这个只能是提供数据，但数据看起来都是抓的是吧？对，但是我们就是因为根据我们有看他们那个robo robotbo tX那个那个文档嘛，就是看这个该网站的一些part取的一些协议，我们有看过。就是哪些是可以拿的，哪些是不可以拿的。然后你们这个公司的商业模式是说实是卖数据嘛？嗯，主要是一个数据服务的。然后另外的话我们有对这个B端用户进行一个定向的开发。主要是一些中小商户是吧？对的，就我们上上一家公司主要是一些企业的一些外包服务嘛。就是啊对对方提出一些需求，我们就是进行一些这个定向的开发。就是这个平台的话，主要就是为了实现这个时效性为主的一个平台。平哥，我先问一个，就是那你第二个那个就是写的那个运动的那块的那个数据，你用牛佛这做的话，你是做的主要是语音对话的，还是那个视频的？呃，我们主要是一个机器人，就是机器人的一个问答的形式，就不是语音对话。机器人の話？就不是语音对话机器人的问他，其实我我理解就是TTSASR那种识别的，然后语音对话是这样的吗？对的对的，就是说用户输入一个文本你们用。你们最初出的文本是什么样的形式的？嗯，最初的文本就是一个word或者PDF的一个形式。然后呃那我想问一下，就是说你你你上面也写了用牛 for j啊，或者是用tept，就是处理数据嘛？你这个处理的数据是怎么你们怎么把它做标注，就是你刚才说的标注和那个啥的分类啊或者怎么样的。嗯。从哪几个维度，这个从嗯这个这个NU4它这个。存储的一些向量向量的数据的话，我们就是包括一些呃，比如他主他他又怎么说。还有怎么说？嗯，就比如说运动运动它有一些实体，因为我们运动医学它这个主要是一个突谱项目嘛，就是有一些实体。那比如说那个前韧带交叉韧带拉伤损伤，然后一些呃呃网球走啊，或者说跑步膝啊，就是那个呃甲情术这些这些拉伤之类的，然后也有对应它的一些关系，就比如说一些营养补剂啊，对应的一些呃症这个疾病症状，然后以及它的一些训练动作。以及它对应的一些机群，我们将样进行一些呃数据的一些相关的存储。然后它的。啊，对那我问一下你你你因为你这上面是写说的是你之前是做标注嘛？对对对，数据标注或者数据请息这一块类的工作嘛。对，就是像这样的运动数据的话，你们是怎么做的，就怎么做清洗和那个啥的。嗯，就是就是你你比如举个例子，你说那个机器人是的问答的嘛。对那那你举个例子，比如说我问一个呃，我如果的体重比较重，我应该做什么样的运动，然后你那边会怎么样的回答呢？你把这个流程就是这个数据流给我说一遍。嗯，从从用户输入的那个时候开始嘛？就是从用户输入的那个时候，从用用户输入，然后走new for这怎么走到Tept，你把整个这个说一遍。嗯，好好的啊，就是当用户输入一个呃这个。问题的时候，比如说这个就像您说的这个呃我体重比较大，需要怎么进行一个运动来进行一个减肥或者减脂这样的一个呃动作的话，我们就先进行一个呃意图识别的一个匹配。就是说呃就就是说这个是否是呃关于这个运动医学这个范畴里面的一些问题。如果不是的话，我们就直接。因为它是不相关的嘛。我们就比如说设置一个默认的回答，就是说对不起您您说的问题，呃，不是医学领运用领域的一个一个问题。就是说请请请就是提问相关的问题。这样的话，然后另外一个就是如果他是他是这个领域的问题的话，我们就进行一个实体他的抽取。他抽取的话，就比如说。呃，体重比较大呃，想减脂。然后这样的话我我们就是进行一个实体抽取，抽取出来的话，我们会进行一个实体的匹配。就是呃因为之前我们存储的一个数在NU4这个数据库里面的话，我们存储了一些呃这个已有的一些数据。然后比如说跑步啊，呃或者说这个呃康复的呃提走这些之类的一些动作，然后还有一些它的热身一些相关事项，我们有进行一些呃数据的存储以及它的向量化。啊，然后这个抽取了用户的一些实体之后进行一些匹配，匹配之后呢，我们会呃进行一个呃sber语句的一个查询这个语语句的生成。它生成之后以后呢，有一个判判断它是否合法。如果它合法的话，就在这个NU for这这个数据库里面进行一个呃。这个检索检索出来结果的话，我们就送入到大模型。在送入到大模型之后，让它生成一个答案就可以了。嗯，您好，这个我的回答完毕，这种跟跟直接送大模型有啥区别吗？嗯，因为它是在一个图谱里面，就是我们之前已经构建了一个知识图谱。这个它里面有关于呃常见的一些运动项目或者损伤类型，还有一些康复动作这样的一些知识嘛。就它有一些关系。比如呃比如说这个跑步它的一些相关的一些事项。比如说您这个这个送宽啊，就是长跑这样的送宽或者是说这个摆臂啊，或者说您这个遇到这个上坡路段的话，他身体的姿势要怎么样的，他他这有一个相关的一个呃一个关系的一个标注。然后以及。另外一些拓展它可以有不同的关系嘛，这样就相当于一个就就您比如说我们家族的突谱，这样子就上一辈到这一辈，父辈祖辈。父背组备这样子对解释这个我的意思是你为什么那你跟直接放DepC有啥区别？呃，就你你你走完这一套跟直接放DepC有啥区别？呃，好的，那个就是你放deep sick deepep sick它基于的是呃某一截止时间，就比如说2024年7月21号这个这个截止时间，它之前的一个训练的内部的训练知识。如果如果在这个时间点之前，他没有更新相应的一些新的知识进去的话。他他这个就就比如说呃。呃，就就比如说这个运动，就比如说篮球，就这个运动项目，它有一些新的规则啊，也有一些新的这个呃损伤的类型啊，这些补剂啊之类的，我们我们就是补充进去嘛。这样的话它的准确性会高一点。他DC他回答的不一定准确。对，然后对，然后他是可能产生一些幻觉嘛，就是这样子。我看你这这就一年的时间做的功能还挺多的啊这个。这里头都是都是深度参与的嘛，这些对我们是第一个第一个那个电商RAG，我是深度参与的。然后那个知识图我我主要是负责一些那个呃关系呃实体和那个关系的抽取，以及那个呃意图识别模块的搭建。然后对这个。这个主要它的流程我有深度参与一见啊，其他的我就没有，然后这个大模型图谱的运动问答，这里头也是从外网抓的一些数据是吧？对的对的。嗯嗯然后现在有多少用户在用啊这个嗯。就我们的用户基数现在是10万左右，因为是刚刚刚这个这个也是刚弄完不久，因为用都用不在用。这么多用户在用啊嗯，健康咨询这个健康咨询差不多的。因为每天他顶峰它顶峰的时间有10万多，就是平常的话它不是很多。就是我刚才以为您说问的是我这个这个最高峰时候，它的流量是有多少，就是就我误解误解用户量大概是用户量大概是多大呃。这个在3万左右吧，3万3万万左右。对，这个是都是在哪个上年的会用啊？是机器狗机器人嗯。它主要也是一个对话形式的一个问答啊，对话形式问答，但是是放到这个。这个这个这个跑步的一些APP上用还是什么意思啊？嗯，对，我们就是比如说呃健身的一些APP或者一些专门运动的一些康复的APP我们有就是说运动的一些网站呢，或者他们会形成一种APP嘛。我们就在里面呃放了这个这个系统。哦哦，那你们是相对于卖个to B端的来省是吧？对，这个主要是to B的哦，那现在有哪几家在用你们这个这个这个问答系统？呃，kiki在用啊，c有的，但是他的用户毕竟他用户基数没有那么大，所以就没有那么出名了。就是有几家在用你们这个问答系统，嗯，大概三四家左右吧，三四家他这个是刚出来的嗯。那他们用的反馈怎么样啊？嗯，就是比如说我们这个系统的话，它也有存在一些问题。就比如说呃。这个营养营养素营养素和这个补剂它们之间的一个成分的一个识别，它的一个关系以及他们这个这个它不是很精准，这个也有一些问题。所以我们的用户反馈的话，主要是这个方面的问题。你们都是在哪些网站上去抓的数据啊？嗯，这个主要是一些这个运动医学的一一些网站嘛，就是比如说呃。中就是外网的一些数据，我们有去看一下的。行好，我这边没有其他问题，然后您看您有什么问题啊。嗯，呃我想问一下这个贵贵司对这个岗位的他一个业务的需求是什么样的？呃我我们也是做一些云端的基术大模型，主要是面向这个。康阳领域的这个医学问答，然后啊主要是加载在这个家用机器人上面，它一是就支持一老一小。嗯，那那他这个机器人的话是一种怎样的一个交互方式呢？就是就比如说像我们这种就是直接输入文本，还是说有语音的识别呢？那有语音的。好好嘞好嘞，嗯，第二个问题就是我想问一下，这个团队大概就是就是负责这一块工作的人大概有多少呢？就目前我们团队一团队一共三两三个吧，目前人比较少，在招。好的好的，嗯，那我这边没有问题了。嗯嗯，好，那我们今天就到这儿。好的好，谢谢你。好，谢谢您谢谢您。嗯，好嗯，拜拜哎。你自己问死了呀。",
        "name": "小明"
    }))
